package it.unicam.cs.cosy.bioshape.core.components;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.vecmath.Point3d;

import vclip.DistanceReport;

/**
 * Represents a couple of (possibly) colliding {@code ShapedEntities}.
 * 
 * @author Matteo Micheletti, Federico Buti
 */
public class EntityPair extends Pair<Entity, Entity> {

    /** default generated serial ID */
    private static final long serialVersionUID = 8402315711309806350L;

    private double distance;
    
    // to save vclip invocations
    private double distanceMiddle;
    private double distanceRight;
    private double left;
    private double middle;
    private double right;

    /**
     * @return the left
     */
    public double getLeft() {
        return left;
    }

    /**
     * @param left the left to set
     */
    public void setLeft(double left) {
        this.left = left;
    }

    /**
     * @return the middle
     */
    public double getMiddle() {
        return middle;
    }

    /**
     * @param middle the middle to set
     */
    public void setMiddle(double middle) {
        this.middle = middle;
    }

    /**
     * @return the right
     */
    public double getRight() {
        return right;
    }

    /**
     * @param right the right to set
     */
    public void setRight(double right) {
        this.right = right;
    }

    // TODO per sapere se sto girando intorno ad un punto di contatto
    // private double distanceBackup = 0;
    // TODO per savarmi le distanze dei closest points
    private double[][] previousDistances = new double[3][2];

    private DistanceReport distanceReport;
    
    // to save vclip invocations
    private DistanceReport distanceReportMiddle;
    private DistanceReport distanceReportRight;

    /**
     * @return the distanceMiddle
     */
    public double getDistanceMiddle() {
        return distanceMiddle;
    }

    /**
     * @param distanceMiddle the distanceMiddle to set
     */
    public void setDistanceMiddle(double distanceMiddle) {
        this.distanceMiddle = distanceMiddle;
    }

    /**
     * @return the distanceRight
     */
    public double getDistanceRight() {
        return distanceRight;
    }

    /**
     * @param distanceRight the distanceRight to set
     */
    public void setDistanceRight(double distanceRight) {
        this.distanceRight = distanceRight;
    }

    /**
     * @return the distanceReportMiddle
     */
    public DistanceReport getDistanceReportMiddle() {
        return distanceReportMiddle;
    }

    /**
     * @param distanceReportMiddle the distanceReportMiddle to set
     */
    public void setDistanceReportMiddle(DistanceReport distanceReportMiddle) {
        this.distanceReportMiddle = distanceReportMiddle;
    }

    /**
     * @return the distanceReportRight
     */
    public DistanceReport getDistanceReportRight() {
        return distanceReportRight;
    }

    /**
     * @param distanceReportRight the distanceReportRight to set
     */
    public void setDistanceReportRight(DistanceReport distanceReportRight) {
        this.distanceReportRight = distanceReportRight;
    }


    /**
     * Builds the pair
     * 
     * @param e1
     *            First {@code Entity}.
     * @param e2
     *            Second {@code Entity}.
     */
    public EntityPair(Entity e1, Entity e2) {
        super(e1, e2);
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = super.hashCode();
        result = prime * result + ((distanceReport == null) ? 0 : distanceReport.hashCode());
        long temp;
        temp = Double.doubleToLongBits(distance);
        result = prime * result + (int) (temp ^ (temp >>> 32));
        result = prime * result + Arrays.hashCode(previousDistances);
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (!super.equals(obj))
            return false;
        if (getClass() != obj.getClass())
            return false;
        EntityPair other = (EntityPair) obj;
        if (distanceReport == null) {
            if (other.distanceReport != null)
                return false;
        } else if (!distanceReport.equals(other.distanceReport))
            return false;
        if (Double.doubleToLongBits(distance) != Double
                .doubleToLongBits(other.distance))
            return false;
        if (!Arrays.equals(previousDistances, other.previousDistances))
            return false;
        return true;
    }

    /**
     * @return the distance between the entities.
     */
    public double getDistance() {
        return distance;
    }

    /**
     * @return the previous distance between the entities.
     */
    public double[][] getPreviousDistances() {
        return previousDistances;
    }

    /**
     * Traces the previous distances among the two {@code Entity} objects. It
     * serves as a workaround for the distance bug of V-Clip. // TODO serve per
     * correggere il bug delle distanze // TODO Luca: cosa è il distance bug di
     * V-Clip? O_O
     * 
     * @param closestPoints
     *            Current closest points set.
     * @param distance
     *            Current distance between the points.
     */
    public void saveClosestPointsDistances(Point3d[] closestPoints,
            double distance) {
        double dist = 0;
        if ((dist = closestPoints[0].getX() - closestPoints[1].getX()) == 0) {
            this.previousDistances[0][0] = 0;
            this.previousDistances[0][1] = dist;
        } else if (Math.abs(dist) == Math.abs(distance)) {
            if (distance < 0)
                this.previousDistances[0][0] = 1;
            else
                this.previousDistances[0][0] = 0;
            this.previousDistances[0][1] = Math.abs(dist);
        }
        if ((dist = closestPoints[0].getY() - closestPoints[1].getY()) == 0) {
            this.previousDistances[1][0] = 0;
            this.previousDistances[1][1] = dist;
        } else if (Math.abs(dist) == Math.abs(distance)) {
            if (distance < 0)
                this.previousDistances[1][0] = 1;
            else
                this.previousDistances[0][0] = 0;
            this.previousDistances[1][1] = Math.abs(dist);
        }

        if ((dist = closestPoints[0].getZ() - closestPoints[1].getZ()) == 0) {
            this.previousDistances[2][0] = 0;
            this.previousDistances[2][1] = dist;
        } else if (Math.abs(dist) == Math.abs(distance)) {
            if (distance < 0)
                this.previousDistances[2][0] = 1;
            else
                this.previousDistances[0][0] = 0;
            this.previousDistances[2][1] = Math.abs(dist);
        }
    }

    /**
     * Set the new distance among the entities.
     * 
     * @param distance
     *            The new distance value.
     */
    public void setDistance(double distance) {
        this.distance = distance;
//        addDistance(distance);
    }

    // /**
    // * Set the previous distances among the entities.
    // *
    // * @param previousDistances
    // * The previous distance values.
    // */
    // public void setPreviousDistances(double[][] previousDistances) {
    // this.previousDistances = previousDistances;
    // }

    /**
     * @return The V-Clip distance report containing all the information about
     *         the closest points among the entities
     */
    public DistanceReport getDistanceReport() {
        return distanceReport;
    }

    /**
     * @param d 
     *            The current new {@code DistanceReport} between the entities.
     */
    public void setDistanceReport(DistanceReport d) {
        this.distanceReport = d;
    }

    @Override
    public String toString() {
        return this.getE1().getCenter() + "   " + this.getE2().getCenter();
    }



//    /**
//     * @param distanceReport
//     */
//    public void addDistance(double distanceReport) {
//        this.listDistances.add(new Double(distanceReport));
//    }
//
//    /**
//     * @return
//     */
//    public List<Double> getDistances() {
//        return this.listDistances;
//    }
//
//    /**
//     * 
//     */
//    public void resetDistances() {
//        this.listDistances.clear();
//    }
//
//    /**
//     * Controls if there is a change of sign in the distances, from negative to
//     * positive
//     * 
//     * @return the position of the first occurrence of the change, -1 if the
//     *         change does not occur
//     */
//    public int isThereRotationTunnelling() {
//        if (this.listDistances.size() <= 1)
//            return -1;
//        boolean positive = this.listDistances.get(0).doubleValue() >= 0;
//        int i = 1;
//        boolean found = false;
//        if (positive) {
//            // search for a negative
//            while (i < this.listDistances.size() && !found)
//                if (this.listDistances.get(i).doubleValue() < 0)
//                    found = true;
//                else
//                    i++;
//            if (!found)
//                return -1;
//            else {
//                i++;
//                // search for a positive
//                while (i < this.listDistances.size() && !found)
//                    if (this.listDistances.get(i).doubleValue() >= 0)
//                        found = true;
//                    else
//                        i++;
//            }
//            if (found)
//                return i;
//            else
//                return -1;
//        } else { // not positive
//                 // search for a negative
//            while (i < this.listDistances.size() && !found)
//                if (this.listDistances.get(i).doubleValue() < 0)
//                    found = true;
//                else
//                    i++;
//            if (found)
//                return i;
//            else
//                return -1;
//        }
//    }
}